数位dp
LeetCode 2801. 统计范围内的步进数字数目
给你两个正整数 low 和 high ,都用字符串表示,请你统计闭区间 [low, high] 内的 步进数字 数目。
如果一个整数相邻数位之间差的绝对值都 恰好 是 1 ,那么这个数字被称为 步进数字 。
请你返回一个整数,表示闭区间 [low, high] 之间步进数字的数目。
由于答案可能很大,请你将它对 1e9 + 7 取余 后返回。
注意:步进数字不能有前导 0 。
示例 1:
输入:low = "1", high = "11"
输出:10
解释:区间 [1,11] 内的步进数字为 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 和 10 。总共有 10 个步进数字。所以输出为 10 。示例 2:
输入:low = "90", high = "101"
输出:2
解释:区间 [90,101] 内的步进数字为 98 和 101 。总共有 2 个步进数字。所以输出为 2 。提示:
- 1 <= int(low) <= int(high) < 10^100
- 1 <= low.length, high.length <= 100
- low 和 high 只包含数字。
- low 和 high 都不含前导 0 。
代码:
cpp
class Solution {
public:
const int MOD = 1e9 + 7;
int dp[110][1 << 10];
// pre: 表示当前位前一位的填了什么数字
// 返回从 i 开始填数字
// is_limit 表示前面填的数字是否都是 n 对应位上, 如果为 true, 那么当前位至多为 int(s[i]), 否则至多为 9
// is_num 表示前面是否填了数字(是否跳过), 如果为 true, 那么当前位可以从 0 开始, 如果为 false, 那么我们可以跳过, 或者从 1 开始填数字
int f(int i, int pre, bool is_limit, bool is_num, string &s) {
if (i == s.size())
return is_num; // 找到了一个合法数字
if (!is_limit && is_num && dp[i][pre]!= -1)
return dp[i][pre];
int res = 0;
if (!is_num) // 可以跳过当前数位
res = f(i + 1, pre, false, false, s);
int down = 1 - is_num;
int up = is_limit ? s[i] - '0' : 9; // 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i] (否则就超过 n 啦)
for (int d = down; d <= up ; d ++ )
if (!is_num || abs(d - pre) == 1)
res = (res + f(i + 1, d, is_limit && d == up, true, s)) % MOD;
if (!is_limit && is_num)
dp[i][pre] = res; // 记忆化
return res;
}
int calc(string &s) {
int n = s.length();
memset(dp, -1, sizeof dp);
return f(0, 0, true, false, s);
}
bool valid(string &s) {
for (int i = 1; i < s.length() ; i ++ )
if (abs(int(s[i]) - int(s[i - 1])) != 1)
return false;
return true;
}
int countSteppingNumbers(string low, string high) {
return (calc(high) - calc(low) + MOD + valid(low)) % MOD;
}
};python
# 灵神
class Solution:
def countSteppingNumbers(self, low: str, high: str) -> int:
MOD = 10 ** 9 + 7
def calc(s: str) -> int:
@cache # 记忆化搜索
def f(i: int, pre: int, is_limit: bool, is_num: bool) -> int:
if i == len(s):
return int(is_num) # is_num 为 True 表示得到了一个合法数字
res = 0
if not is_num: # 可以跳过当前数位
res = f(i + 1, pre, False, False)
low = 0 if is_num else 1 # 如果前面没有填数字,必须从 1 开始(因为不能有前导零)
up = int(s[i]) if is_limit else 9 # 如果前面填的数字都和 n 的一样,那么这一位至多填 s[i](否则就超过 s 啦)
for d in range(low, up + 1): # 枚举要填入的数字 d
if not is_num or abs(d - pre) == 1: # 第一位数字随便填,其余必须相差 1
res += f(i + 1, d, is_limit and d == up, True)
return res % MOD
return f(0, 0, True, False)
return (calc(high) - calc(str(int(low) - 1))) % MOD